package com.hzlj.position.job.job;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ObjectUtil;
import com.fz.common.base.core.RequestUtil;
import com.fz.common.dict.annotation.DictConvert;
import com.fz.common.dict.dto.DictionaryMenuDTO;
import com.fz.common.dict.service.DictionaryService;
import com.hzlj.position.config.common.dto.common.LocateTimerTask;
import com.hzlj.position.config.common.dto.common.PositionAddDTO;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBind.JzJbxxDeviceBindQueryResultDTO;
import com.hzlj.position.config.common.dto.jzJbxxDeviceBind.JzJbxxDeviceBindSimpleQueryParamDTO;
import com.hzlj.position.config.common.dto.wdConfig.WdConfigQueryResultDTO;
import com.hzlj.position.config.config.PositionConfig;
import com.hzlj.position.config.rest.wd.WdLocateRest;
import com.hzlj.position.config.service.JzJbxxDeviceBindService;
import com.hzlj.position.config.service.WdConfigService;
import com.hzlj.position.locate.common.dto.position.PositionAddResultDTO;
import com.hzlj.position.locate.service.DifferentiationDataService;
import com.hzlj.position.locate.service.PositionAddTaskService;
import com.hzlj.position.locate.service.PositionNewestService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * 获取位置的定时作业
 **/
@Slf4j
@Component
public class WdTimerTaskJob implements LocateTimerTaskJob {
    @Resource
    private JzJbxxDeviceBindService deviceBindService;
    @Resource
    private PositionAddTaskService positionAddTaskService;
    @Resource
    private PositionConfig positionConfig;
    @Resource
    private WdConfigService wdConfigService;
    @Resource
    private DictionaryService dictionaryService;
    @Resource
    private PositionNewestService positionNewestService;
    @Resource
    private DifferentiationDataService differentiationDataService;
    /**
     * 获取定位数据
     * 并且有是否需要重新定位的逻辑
     */
    private final Consumer<LocateTimerTask.Task> positionRequest = (task) -> {
        LocateTimerTask.Task.Key taskKey = task.getKey();
        PositionAddDTO position = fetchPosition(taskKey);
        if (position == null) {
            return;
        }
        LocateTimerTask.Task.Key key = task.getKey();
        WdConfigService.WdTaskConfig wdTaskConfig = wdConfigService.taskConfig(key.getJgCode(), key.getChannel());
        ScheduledExecutorService delayQueue = wdTaskConfig.getDelayQueue();
        //是否需要延迟调度
        if (delayQueue == null) {
            positionAddTaskService.add(position);
            return;
        }
        //需要有延迟队列
        wdTaskConfig.getDelayQueue().schedule(() -> {
            PositionAddDTO delayPosition = fetchPosition(taskKey);
            if (delayPosition != null) {
                positionAddTaskService.add(position);
            }
        }, wdTaskConfig.getConfig().getRequestDelaySeconds(), TimeUnit.SECONDS);
    };


    @PostConstruct
    private void start() {
        //立即执行
        ThreadUtil.execAsync(() -> start(false));
        ThreadUtil.execAsync(this::startAlarms);
        //每30分钟跑一次
        ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
        scheduled.scheduleAtFixedRate(
                () -> start(true),
                30,
                30,
                TimeUnit.MINUTES
        );
    }


    /**
     * 实时获取定位
     */
    @DictConvert
    public PositionAddResultDTO fetchPositionNewest(String jgCode,
                                                    String jzId,
                                                    String deviceCode,
                                                    String channel) {
        LocateTimerTask.Task.Key taskKey = new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel);
        PositionAddDTO position = fetchPosition(taskKey);

        WdConfigService.WdTaskConfig wdTaskConfig = wdConfigService.taskConfig(jgCode, channel);
        ScheduledExecutorService delayQueue = wdTaskConfig.getDelayQueue();
        if (delayQueue == null) {
            if (position != null) {
                position.setReturnPosition(true);
            }
            return positionAddTaskService.add(position);
        }

        //需要有延迟队列
        Date now = new Date();
        wdTaskConfig.getDelayQueue().schedule(() -> {
            int delaySeconds = 0;
            try {
                while (true) {
                    PositionAddDTO delayPosition = fetchPosition(taskKey);
                    if (delaySeconds < wdTaskConfig.getConfig().getRequestDelaySeconds()
                            && (delayPosition == null || delayPosition.getPositionTime().compareTo(now) < 0)) {
                        ThreadUtil.sleep(5, TimeUnit.SECONDS);
                    } else {
                        positionAddTaskService.add(delayPosition);
                        break;
                    }
                    delaySeconds += 5;
                }
            } catch (Exception e) {
                log.warn("获取腕带最新位置信息出错:deviceCode={}", deviceCode);
            }
        }, 5, TimeUnit.SECONDS);
        return PositionAddResultDTO.fail("等待腕带返回最新定位数据");
    }


    /**
     * 实时获取定位
     */
    @DictConvert
    public PositionAddResultDTO fetchPosition(String jgCode,
                                              String jzId,
                                              String deviceCode,
                                              String channel) {
        PositionAddDTO position = fetchPosition(new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel));
        if (ObjectUtil.isNull(position)) {
            return PositionAddResultDTO.fail("定位数据为空");
        }
        //添加到队列中
        position.setReturnPosition(true);
        return positionAddTaskService.add(position);
    }

    public void cancelTask(String jgCode,
                           String jzId,
                           String deviceCode,
                           String channel) {
        LocateTimerTask.Task task = new LocateTimerTask.Task(
                new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel)
        );
        LocateTimerTask.cancel(task);
    }

    public void addTask(String jgCode,
                        String jzId,
                        String deviceCode,
                        String channel,
                        Integer interval) {
        addTask(jgCode, jzId, deviceCode, channel, interval, 0, false);
    }

    public void addTask(String jgCode,
                        String jzId,
                        String deviceCode,
                        String channel,
                        Integer interval,
                        Integer delay,
                        boolean addIfAbsent) {
        LocateTimerTask.Task task = new LocateTimerTask.Task(
                new LocateTimerTask.Task.Key(jgCode, jzId, deviceCode, channel),
                interval,
                delay
        );
        WdConfigService.WdTaskConfig wdTaskConfig = wdConfigService.taskConfig(jgCode, channel);
        if (wdTaskConfig == null) {
            return;
        }
        if (addIfAbsent) {
            LocateTimerTask.addIfAbsent(task, wdTaskConfig.getTaskQueue());
        } else {
            LocateTimerTask.add(task, wdTaskConfig.getTaskQueue());
        }
    }

    public void updateLocateInterval(String jgCode,
                                     String jzId,
                                     String deviceCode,
                                     String channel,
                                     Integer locateInterval) {
        WdConfigService.WdTaskConfig wdTaskConfig = wdConfigService.taskConfig(jgCode, channel);
        if (wdTaskConfig == null) {
            return;
        }
        WdConfigQueryResultDTO config = wdTaskConfig.getConfig();
        //修改间隔
        wdTaskConfig.getWdLocateRest().updateLocateInterval(
                config,
                jgCode,
                deviceCode,
                locateInterval
        );
    }


    private void startAlarms() {
        List<String> channels = wdConfigService.channels();
        List<WdConfigService.WdTaskConfig> wdTaskConfigs = wdConfigService.taskConfigs();
        if (!positionConfig.isWdTask() || ObjectUtil.isEmpty(channels)) {
            log.info("不用开启实时腕带定位作业");
            return;
        }
        for (WdConfigService.WdTaskConfig wdTaskConfig : wdTaskConfigs) {
            Integer requestAlarmsSeconds = wdTaskConfig.getConfig().getRequestAlarmsSeconds();
            ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
            scheduled.scheduleAtFixedRate(() -> {
                        try {
                            Date endTime = new Date();
                            WdLocateRest wdLocateRest = wdTaskConfig.getWdLocateRest();
                            List<PositionAddDTO> positions = wdLocateRest.fetchAlarmsPositions(
                                    wdTaskConfig.getConfig(),
                                    wdTaskConfig.getConfig().getJgCode(),
                                    DateUtil.offsetSecond(endTime, -requestAlarmsSeconds),
                                    DateUtil.offsetSecond(endTime, -1),
                                    (type, code) -> {
                                        DictionaryMenuDTO dict = dictionaryService.getByTypeCode(RequestUtil.getAppId(), type, code);
                                        return dict == null ? null : dict.getName();
                                    }
                            );
                            //添加报警
                            if (ObjectUtil.isNotEmpty(positions)) {
                                for (PositionAddDTO position : positions) {
                                    positionAddTaskService.add(position);
                                }
                            }
                        } catch (Exception e) {
                            log.warn("定时获取腕带报警信息出错:config={}", wdTaskConfig.getConfig());
                        }
                    },
                    0,
                    requestAlarmsSeconds,
                    TimeUnit.SECONDS);
        }
    }


    private void start(boolean addIfAbsent) {
        List<String> channels = wdConfigService.channels();
        List<WdConfigService.WdTaskConfig> wdTaskConfigs = wdConfigService.taskConfigs();
        if (!positionConfig.isWdTask() || ObjectUtil.isEmpty(channels)) {
            log.info("不用开启实时腕带定位作业");
            return;
        }
        //启动作业
        LocateTimerTask.start(new ArrayList<>(wdTaskConfigs), positionRequest);
        //获取所有的绑定数据
        List<JzJbxxDeviceBindQueryResultDTO> devices = deviceBindService.listJzJbxxDeviceBind(
                new JzJbxxDeviceBindSimpleQueryParamDTO().setChannels(channels).setMdy(false)
        );
        log.info("开启腕带定位任务,size={}", devices.size());
        //添加到监控队列中
        for (JzJbxxDeviceBindQueryResultDTO device : devices) {
            try {
                int interval = device.getLocateInterval();
                int delay = getLocateDelay(positionNewestService, differentiationDataService, device);
                addTask(device.getJgCode(), device.getJzId(), device.getDeviceCode(), device.getChannel(), interval, delay, addIfAbsent);
            } catch (Exception e) {
                log.error("开启腕带定位任务失败,device={}", device, e);
            }
        }
    }

    @PreDestroy
    private void stop() {
        LocateTimerTask.stop();
    }

    private PositionAddDTO fetchPosition(LocateTimerTask.Task.Key key) {
        //获取位置
        try {
            WdConfigService.WdTaskConfig wdTaskConfig = wdConfigService.taskConfig(key.getJgCode(), key.getChannel());
            if (wdTaskConfig == null) {
                return null;
            }
            WdConfigQueryResultDTO config = wdTaskConfig.getConfig();
            return wdTaskConfig.getWdLocateRest().fetchPosition(
                    config,
                    key.getJgCode(),
                    key.getDeviceCode(),
                    (type, code) -> {
                        DictionaryMenuDTO dict = dictionaryService.getByTypeCode(RequestUtil.getAppId(), type, code);
                        return dict == null ? null : dict.getName();
                    }
            );
        } catch (Throwable e) {
            log.error("腕带定位异常:{}->{}", key.getDeviceCode(), key.getChannel(), e);
        }
        return null;
    }

}
